package org.shagin.jfin.date.cond.xml;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Currency;
import java.util.List;

import org.apache.commons.lang3.StringUtils;
import org.shagin.jfin.data.Money;
import org.shagin.jfin.data.model.IBudget;
import org.shagin.jfin.data.model.OperationRuleAction;
import org.shagin.jfin.date.DateUtils;
import org.shagin.jfin.date.cond.Daily;
import org.shagin.jfin.date.cond.ExactDate;
import org.shagin.jfin.date.cond.IDateCondition;
import org.shagin.jfin.date.cond.Monthly;
import org.shagin.jfin.date.cond.Never;
import org.shagin.jfin.date.cond.Weekly;
import org.shagin.jfin.date.cond.Yearly;
import org.shagin.xml.XMLUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Node;

/**
 * The Class OperationRuleXmlParser.
 */
public class OperationRuleXmlParser {
    private static final Logger log = LoggerFactory
            .getLogger(OperationRuleXmlParser.class);

    private static final String WHEN = "when";
    private static final String VALUE = "value";
    private static final String TO = "to";
    private static final String FROM = "from";

    private IBudget dataSource;
    private String ruleId;
    private List<OperationRuleAction> ruleActions = new ArrayList<OperationRuleAction>();

    /**
     * Instantiates a new operation rule xml parser.
     * 
     * @param node the node
     * @param dataSource the data source
     */
    public OperationRuleXmlParser(Node node, IBudget dataSource) {
        this.dataSource = dataSource;
        parseXml(node);
    }

    /**
     * Parses the xml.
     * 
     * @param node the node
     */
    void parseXml(Node node) {
        ruleId = XMLUtils.getNodeId(node);
        double amount = XMLUtils.getNodeAttributeDouble(node, VALUE, 0d);
        parseOperationRuleActions(node, amount);
    }

    /**
     * Gets the currency.
     * 
     * @param dataSource the data source
     * @param node the node
     * @return the currency
     */
    public static Currency getCurrency(IBudget dataSource, Node node) {
        // TODO
        return dataSource.getDefaultCurrency();
    }

    /**
     * Parses the operation rule actions.
     * 
     * @param node the node
     * @param defAmount the def amount
     */
    private void parseOperationRuleActions(Node node, double defAmount) {
        List<Node> childs = XMLUtils.getChildNodes(node, WHEN);

        for (Node child : childs) {
            IDateCondition dc = parseDateCondition(child);
            double amount = XMLUtils.getNodeAttributeDouble(child, VALUE,
                    defAmount);
            Money m = new Money(getCurrency(dataSource, child), amount);
            ruleActions.add(new OperationRuleAction(dc, m));
        }
    }

    /**
     * Split type definition.
     * 
     * @param s the s
     * @return the string[]
     */
    private static String[] splitTypeDefinition(String s) {
        String[] result = new String[2];
        result[0] = s;
        result[1] = "";
        int i1 = s.indexOf("(");
        int i2 = s.indexOf(")");
        if (i1 > 0 && (i1 < i2)) {
            result[0] = s.substring(0, i1);
            result[1] = s.substring(i1 + 1, i2);
        }
        return result;
    }

    /**
     * Parses the date condition.
     * 
     * @param node the node
     * @return the i date condition
     */
    private static IDateCondition parseDateCondition(Node node) {
        IDateCondition result = new Never();

        String sDateStart = XMLUtils.getNodeAttribute(node, FROM,
                StringUtils.EMPTY);
        String sDateEnd = XMLUtils
                .getNodeAttribute(node, TO, StringUtils.EMPTY);

        String typeDefinition = XMLUtils.getNodeAttribute(node, "type");
        String[] vals = splitTypeDefinition(typeDefinition);
        String type = vals[0];
        String typeArgs = vals[1];

        if (type.equalsIgnoreCase("ExactDate")) {
            result = new ExactDate(typeArgs);
        } else if (type.equalsIgnoreCase("Daily")) {
            result = new Daily();
        } else if (type.equalsIgnoreCase("Weekly")) {
            result = new Weekly(argsToWeeks(typeArgs));
        } else if (type.equalsIgnoreCase("Monthly")) {
            result = new Monthly(argsToMonths(typeArgs));
        } else if (type.equalsIgnoreCase("Yearly")) {
            int[] v = argsToYears(typeArgs);
            int month = v[0];
            int m = 1;
            if (v.length > 1)
                m = v[1];
            result = new Yearly(month, m);
        }

        if (!StringUtils.isEmpty(sDateStart)) {
            result.getRange().setStart(DateUtils.parse(sDateStart));
        }

        if (!StringUtils.isEmpty(sDateEnd)) {
            result.getRange().setEnd(DateUtils.parse(sDateEnd));
        }

        if (log.isDebugEnabled() && !StringUtils.isEmpty(sDateStart + sDateEnd)) {
            log.debug("Range: " + result.getRange());
        }

        return result;
    }

    /**
     * Args to months.
     * 
     * @param s the s
     * @return the int[]
     */
    private static int[] argsToMonths(String s) {
        String[] v = s.split(",");
        int[] result = new int[v.length];
        for (int i = 0; i < v.length; i++)
            result[i] = toMonth(v[i]);
        log.trace("Monthly: " + s + "=" + intArrayToString(result));
        return result;
    }

    /**
     * Args to weeks.
     * 
     * @param s the s
     * @return the int[]
     */
    private static int[] argsToWeeks(String s) {
        int[] result = new int[0];

        if (s.equals("WORKING_DAYS"))
            result = Weekly.WORKING_DAYS;
        else if (s.equals("WEEK_END"))
            result = Weekly.WEEK_END;
        else {
            String[] v = s.split(",");
            result = new int[v.length];
            for (int i = 0; i < v.length; i++)
                result[i] = toWeekDay(v[i]);
        }

        log.trace("Weekly: " + s + "=" + intArrayToString(result));
        return result;
    }

    /**
     * Int array to string.
     * 
     * @param ints the ints
     * @return the string
     */
    private static String intArrayToString(int[] ints) {
        StringBuilder sb = new StringBuilder("[");
        for (int i = 0; i < ints.length; i++) {
            sb.append(ints[i]);
            if (i < ints.length - 1)
                sb.append(",");
        }
        return sb.append("]").toString();
    }

    /**
     * Args to years.
     * 
     * @param s the s
     * @return the int[]
     */
    private static int[] argsToYears(String s) {
        String[] v = s.split(",");
        int[] result = new int[v.length];
        for (int i = 0; i < v.length; i++)
            result[i] = toMonth(v[i]);
        log.trace("Yearly: " + s + "=" + intArrayToString(result));
        return result;
    }

    /**
     * To month.
     * 
     * @param s the s
     * @return the int
     */
    private static int toMonth(String s) {
        int result = 0;
        s = s.trim().toUpperCase();

        if (s.equals("JANUARY"))
            result = Calendar.JANUARY;
        else if (s.equals("FEBRUARY"))
            result = Calendar.FEBRUARY;
        else if (s.equals("MARCH"))
            result = Calendar.MARCH;
        else if (s.equals("APRIL"))
            result = Calendar.APRIL;
        else if (s.equals("MAY"))
            result = Calendar.MAY;
        else if (s.equals("JUNE"))
            result = Calendar.JUNE;
        else if (s.equals("JULY"))
            result = Calendar.JULY;
        else if (s.equals("AUGUST"))
            result = Calendar.AUGUST;
        else if (s.equals("SEPTEMBER"))
            result = Calendar.SEPTEMBER;
        else if (s.equals("OCTOBER"))
            result = Calendar.OCTOBER;
        else if (s.equals("NOVEMBER"))
            result = Calendar.NOVEMBER;
        else if (s.equals("DECEMBER"))
            result = Calendar.DECEMBER;
        else
            result = Integer.valueOf(s);
        return result;
    }

    /**
     * To week day.
     * 
     * @param s the s
     * @return the int
     */
    private static int toWeekDay(String s) {
        int result = 0;
        s = s.trim().toUpperCase();

        if (s.equals("MONDAY"))
            result = Calendar.MONDAY;
        else if (s.equals("TUESDAY"))
            result = Calendar.TUESDAY;
        else if (s.equals("WEDNESDAY"))
            result = Calendar.WEDNESDAY;
        else if (s.equals("THURSDAY"))
            result = Calendar.THURSDAY;
        else if (s.equals("FRIDAY"))
            result = Calendar.FRIDAY;
        else if (s.equals("SATURDAY"))
            result = Calendar.SATURDAY;
        else if (s.equals("SUNDAY"))
            result = Calendar.SUNDAY;
        else
            result = Integer.valueOf(s);
        return result;
    }

    /**
     * Gets the rule id.
     * 
     * @return the rule id
     */
    public String getRuleId() {
        return ruleId;
    }

    /**
     * Gets the rule actions.
     * 
     * @return the rule actions
     */
    public List<OperationRuleAction> getRuleActions() {
        return ruleActions;
    }
}
